home *** CD-ROM | disk | FTP | other *** search
/ Interactive Web Graphics with Shout 3D / Interactive Web Graphics With Shout 3D.iso / mac / Shout3Ddemo / S3D_2E1.exe / Shout3d_runtime / codebase / applets / ExaminePanel.java < prev    next >
Text File  |  2000-11-10  |  25KB  |  674 lines

  1. /**    
  2.     Company:        Eyematic Interfaces
  3.     Project:        Shout3D 2.0 Sample Code
  4.     Class:            ExaminePanel
  5.     Date:            September 15, 1999
  6.     Description:    Class for ExaminePanel
  7.     (C) Copyright Eyematic Interfaces, Inc. - 1997-2000 - All rights reserved
  8.  */
  9.  
  10. package applets;
  11.  
  12. import shout3d.core.*;
  13. import shout3d.math.*;
  14. import shout3d.*;
  15.  
  16. /**
  17.  * Shout3D ExaminePanel. This class provides the user with the ability to 
  18.  * examine an item. It is similar
  19.  * to but not exactly like, VRML "EXAMINE" mode.
  20.  * 
  21.  * Basic Controls:
  22.  * -- rot = drag
  23.  * -- pan = <shift>+drag
  24.  * -- zoom = <control>+drag
  25.  * 
  26.  * Drag Styles
  27.  * Mouse motion is interpreted via one of two dragStyles. 
  28.  * The dragStyle may be changed with an applet parameter.
  29.  * -- "by speed" changes values over time by a velocity.
  30.  *    The velocity is proportional to how far the mouse has been moved
  31.  *    since mouse down.
  32.  * -- "by offset" changes the value once per mouse-movement.
  33.  *    The value is saved upon mouse-down, and with each move, the new
  34.  *    value is set to be the initial value plus an offset that is
  35.  *    proportional to how far the mouse has been moved since mouse down.
  36.  * 
  37.  * Rotation Center.
  38.  * -- by default, the camera rotates about the center of the scene's bounding box.
  39.  * -- the rotationCenter applet parameter lets you change this center.
  40.  * 
  41.  * Framing the Scene:
  42.  * -- The starting distance from the camera to the rotation center is calculated
  43.  *    to include the entire scene within frame limits, within an offset of backupSlack
  44.  *    to allow adjustment.  See the backupSlack applet parameter below.
  45.  * 
  46.  * Cameras & Scenes:
  47.  * -- Works fine if you bind a new camera or change scenes.
  48.  * -- resetCamera() method returns to the original setting of the camera.
  49.  * 
  50.  * Applet Parameters:
  51.  * -- dragStyle (default = "by speed")
  52.  *    Values are "by speed" or "by offset", corresponding to the drag styles described above
  53.  * -- rotationCenter (default = undefined, meaning use scene bbox center)
  54.  *    If present, the parameter should be 3 floats, for example: "0 0 0"
  55.  *    If present, rotation is about this worldspace point instead of the scene's bbox center
  56.  * -- backupSlack (default = 0.1)
  57.  *    For framing the scene in the panel.
  58.  *    If 0, then the scene will fit the frame as exactly as possible.
  59.  *    If > 0, then the camera will be backed up further to show more.  The value is multiplied
  60.  *    by the original framing distance to get a new one. For example, if a distance of 10 frames
  61.  *    perfectly, then a backupSlack of 0.1 means that the new distance will be 11.
  62.  * -- bySpeedZoomDragFactor        (default = .05)
  63.  * -- bySpeedPanDragFactor        (default = .025)
  64.  * -- bySpeedRotateDragFactor    (default = .033)
  65.  *    The above three parameters only apply when dragStyle is "by speed."  They specify the
  66.  *    amount of zoom, pan, or rotate velocity per pixel moved. For example, dragging 100 pixels
  67.  *    in zoom mode moves the camera at 5 units/second.
  68.  * -- byOffsetZoomDragFactor        (default = .01)
  69.  * -- byOffsetPanDragFactor        (default = .005)
  70.  * -- byOffsetRotateDragFactor        (default = .016)
  71.  *    The above three parameters only apply when dragStyle is "by offset."  They specify the
  72.  *    total change in zoom, pan, or rotation per pixel moved. For example, dragging 100 pixels
  73.  *    in zoom mode moves the camera by 5 units.
  74.  * 
  75.  * @author Jim Stewartson
  76.  * @author Paul Isaacs
  77.  * @author Dave Westwood
  78.  * @author Rory Lane Lutter
  79.  */
  80.  
  81. public class ExaminePanel extends Shout3DPanel implements DeviceObserver {
  82.  
  83.     /////////////////////////////////////////////////////////////
  84.     // Variables set via applet parameters
  85.     // 
  86.     // See comments at top of file for more info.
  87.     /////////////////////////////////////////////////////////////
  88.     // Selection of mapping between mouse motion and the camera.
  89.     // See comments at top of file.
  90.     static public final int BY_SPEED = 0;
  91.     static public final int BY_OFFSET = 1;
  92.     int currentDragStyle = BY_SPEED;
  93.     // Center of rotation.  If null, bbox center will be used.
  94.     float[] rotationCenter = null;
  95.     // For framing the scene.
  96.     float backupSlack = 0.1f;
  97.     // Speeds for "by speed" dragStyle
  98.     float bySpeedZoomDragFactor = .05f;
  99.     float bySpeedPanDragFactor = .025f;
  100.     float bySpeedRotateDragFactor = .033f;
  101.     // Speeds for byOffset dragStyle
  102.     float byOffsetZoomDragFactor = .01f;
  103.     float byOffsetPanDragFactor = .005f;
  104.     float byOffsetRotateDragFactor = .016f;
  105.  
  106.     
  107.     ///////////////////////////////////////////////////////////////////
  108.     // Public methods to call to control the behavior
  109.     ///////////////////////////////////////////////////////////////////
  110.     /**
  111.      * call this whenever you want to go reset the camera.
  112.      * Result is that camera goes back to its initial orientation,
  113.      * 
  114.      * Position is chosen to look at the current center of rotation.  In the
  115.      * case where objects are moving, this may be different than it was 
  116.      * when the scene was first loaded.
  117.      */
  118.     public void resetCamera(){
  119.         wantToReset = true;
  120.     }
  121.     
  122.     ///////////////////////////////////////////////////////////////////
  123.     // Protected member variables used in doing calculations
  124.     ///////////////////////////////////////////////////////////////////
  125.     
  126.     // The three modes of manipulation for the camera:
  127.     static final int PAN    = 0;
  128.     static final int ROTATE = 1;
  129.     static final int ZOOM   = 2;
  130.     // The mode of manipulation currently in use.
  131.     int currentMode;
  132.     
  133.     // cached reference to the initial viewpoint
  134.     Viewpoint camera;
  135.     Node[]    pathToCameraParent = null;
  136.     // cached reference to the scene root
  137.     Transform root;
  138.         
  139.     // These store the initial cursor location of a click-drag-release sequence.
  140.     float startX = 0;
  141.     float startY = 0;    
  142.     
  143.     // This holds the value of the camera's position relative to the
  144.     // center of rotation, prior to rotation about that center.
  145.     // The x,y values hold any x or y panning that's been done.
  146.     // The z value holds the distance back from the bbox center, modified by
  147.     // any zooming that's been done.
  148.     float[] unrotatedCameraOffset = new float[3];
  149.     // This is what the unrotatedCameraOffset turns into after being rotated
  150.     // by the camera's orientation.  Add this to the center of rotation to determine
  151.     // where the camera should be placed.
  152.     float[] rotatedCameraOffset = new float[3];
  153.     
  154.     // These hold values of the camera when the mouse was first pressed 
  155.     // during a click-drag-release sequence
  156.     float[] startUnrotatedCameraOffset = new float[3];
  157.     float[] startHeadingPitchRoll = new float[3];    
  158.             
  159.  
  160.     // Used for rotational calculations
  161.     Quaternion tempQuat = new Quaternion();
  162.     
  163.     // speed values for manipulating camera
  164.     float cameraXPanSpeed = 0;    
  165.     float cameraYPanSpeed = 0;        
  166.     float cameraHeadingSpeed = 0;
  167.     float cameraPitchSpeed = 0;
  168.     float cameraZoomSpeed = 0;
  169.     
  170.     
  171.     float cameraHeading = 0;
  172.     float cameraPitch = -0.2f;
  173.     float cameraRoll = 0;
  174.     float cameraDist = 0;
  175.     
  176.     float[] bboxMin;
  177.     float[] bboxMax;
  178.     float[] bboxCenter;
  179.     
  180.     // For resetting the camera when resetCamera() is called.
  181.     boolean wantToReset = false;
  182.     boolean gotInitCameraOrientation = false;
  183.     float[] initCameraOrientation = { 0, 0, 0, 0 };    
  184.     
  185.     /**
  186.      * Constructs an ExaminePanel
  187.      */
  188.     public ExaminePanel(Shout3DApplet applet){
  189.         super(applet);
  190.     }
  191.     /**
  192.      * Constructs an ExaminePanel
  193.      * 
  194.      * @param applet the Shout3DApplet in which this panel is to be drawn
  195.      * @param width the width of the panel in pixels
  196.      * @param height the height of the panel in pixels
  197.      */
  198.     public ExaminePanel(Shout3DApplet applet, int width, int height){
  199.         super(applet,width,height);
  200.     }
  201.  
  202.     /**
  203.      * Constructs an ExaminePanel
  204.      * 
  205.      * @param applet the Shout3DApplet in which this panel is to be drawn
  206.      * @param x the x position of the panel in pixels
  207.      * @param y the x position of  the panel in pixels
  208.      * @param width the width of the panel in pixels
  209.      * @param height the height of the panel in pixels
  210.      */
  211.     public ExaminePanel(Shout3DApplet applet, int x, int y, int width, int height){
  212.         super(applet,x,y,width,height);
  213.     }
  214.     
  215.     /**
  216.      * Remove observers when done with the panel
  217.      */
  218.     public void finalize()throws Throwable {
  219.         applet.getDeviceListener().removeDeviceObserver(this, "DeviceInput");
  220.         applet.getRenderer().removeRenderObserver(this);        
  221.         super.finalize();
  222.     }
  223.     
  224.     // a float field being used only to convert the javascript string 
  225.     //into a float[] in the init rotationCenter param
  226.     FloatArrayField  rotationCenterField    = new FloatArrayField(getScene(), "rotationCenterField", 0, (new float[]{0.0f, 0.0f, 0.0f})); 
  227.     
  228.     /**
  229.      * Overrides Shout3DPanel.customInitialize()
  230.      */
  231.     public void customInitialize() {
  232.         camera = (Viewpoint)(applet.getCurrentBindableNode("Viewpoint"));
  233.         root = (Transform)getScene();
  234.         
  235.         // Get applet parameters
  236.         String valueString;
  237.         valueString = applet.getParameter("dragStyle");
  238.         if (valueString != null && 
  239.             (valueString.equals("by offset") || valueString.equals("by_offset") || 
  240.              valueString.equals("BY OFFSET") || valueString.equals("BY_OFFSET"))){
  241.             currentDragStyle = BY_OFFSET;
  242.         }
  243.         
  244.         valueString = applet.getParameter("rotationCenter");
  245.         if (valueString != null){
  246.             rotationCenterField.setValueByString(valueString);
  247.             rotationCenter = rotationCenterField.getValue();
  248.         }        
  249.         valueString = applet.getParameter("backupSlack");
  250.         if (valueString != null){
  251.             backupSlack = Float.valueOf(valueString).floatValue();
  252.         }
  253.         
  254.         valueString = applet.getParameter("bySpeedZoomDragFactor");
  255.         if (valueString != null){
  256.             bySpeedZoomDragFactor = Float.valueOf(valueString).floatValue();
  257.         }
  258.         valueString = applet.getParameter("bySpeedPanDragFactor");
  259.         if (valueString != null){
  260.             bySpeedPanDragFactor = Float.valueOf(valueString).floatValue();
  261.         }
  262.         valueString = applet.getParameter("bySpeedRotateDragFactor");
  263.         if (valueString != null){
  264.             bySpeedRotateDragFactor = Float.valueOf(valueString).floatValue();
  265.         }
  266.         
  267.         valueString = applet.getParameter("byOffsetZoomDragFactor");
  268.         if (valueString != null){
  269.             byOffsetZoomDragFactor = Float.valueOf(valueString).floatValue();
  270.         }
  271.         valueString = applet.getParameter("byOffsetPanDragFactor");
  272.         if (valueString != null){
  273.             byOffsetPanDragFactor = Float.valueOf(valueString).floatValue();
  274.         }
  275.         valueString = applet.getParameter("byOffsetRotateDragFactor");
  276.         if (valueString != null){
  277.             byOffsetRotateDragFactor = Float.valueOf(valueString).floatValue();
  278.         }
  279.         
  280.         // register for device input
  281.         applet.getDeviceListener().addDeviceObserver(this, "DeviceInput", null);
  282.         // register for rendering notification
  283.         applet.getRenderer().addRenderObserver(this, null);        
  284.     }
  285.     
  286.     /**
  287.      * DeviceObserver method
  288.      */
  289.     public boolean onDeviceInput(DeviceInput di, Object userData){
  290.         if (di instanceof MouseInput){
  291.             MouseInput mi  = (MouseInput)di;
  292.             switch (mi.which){
  293.             case MouseInput.DOWN:
  294.                 onMouseDown(mi);
  295.                 break;
  296.             case MouseInput.DRAG:
  297.                 onMouseDrag(mi);
  298.                 break;
  299.             case MouseInput.UP:
  300.                 onMouseUp(mi);
  301.                 break;
  302.                 
  303.             }
  304.         }
  305.         
  306.         // return false, let other entities handle the event too.
  307.         return false;
  308.     }
  309.     
  310.     /**
  311.      * Called when the mouse is pressed
  312.      */
  313.     void onMouseDown(MouseInput mi){
  314.         // Save the initial X and Y positions of the cursor.
  315.         startX = mi.x;
  316.         startY = mi.y;
  317.  
  318.         // Based on the modifiers, determine the mode of motion:
  319.         currentMode = getManipulationMode(mi);
  320.         
  321.         if (currentDragStyle == BY_OFFSET){
  322.             // For "by offset" dragging, we need to remember the 
  323.             // starting unrotatedCameraOffset and the starting euler parameters.
  324.             for(int i = 0; i< 3; i++){
  325.                 startUnrotatedCameraOffset[i] = unrotatedCameraOffset[i];    
  326.             }
  327.             // convert the camera's orientation at the beginning into euler parameters.
  328.             tempQuat.setAxisAngle(camera.orientation.getValue());
  329.             tempQuat.getEulers(startHeadingPitchRoll);                
  330.         }
  331.     }
  332.  
  333.     /**
  334.      * Called when the mouse is dragged
  335.      */
  336.     void onMouseDrag(MouseInput mi){
  337.     
  338.         // If the user has changed modes by pressing or releasing a modifier key,
  339.         // we need to simulate the ending of the old drag and the beginning of a 
  340.         // new one in order to get a non-jumpy transition.
  341.         // This is easily achieved by calling onMouseUp, then onMouseDown, 
  342.         // then continuing on as usual.
  343.         if (currentMode != getManipulationMode(mi)){
  344.             onMouseUp(mi);
  345.             onMouseDown(mi);
  346.         }
  347.  
  348.         if(currentDragStyle == BY_SPEED){
  349.             dragMouseBySpeedStyle(mi);
  350.         }
  351.         else if (currentDragStyle == BY_OFFSET) {
  352.             dragMouseByOffsetStyle(mi);
  353.         }
  354.     }
  355.     
  356.     /**
  357.      * In this dragStyle, velocities are set for each of the types of motion.
  358.      * Then, in onPreRender, the velocities are multiplied by time to 
  359.      * produce a resulting change from frame to frame.
  360.      */
  361.     void dragMouseBySpeedStyle(MouseInput mi){
  362.         // Start by setting all speeds to 0.
  363.         cameraXPanSpeed = 0;
  364.         cameraYPanSpeed = 0;
  365.         cameraHeadingSpeed = 0;
  366.         cameraPitchSpeed = 0;
  367.         cameraZoomSpeed = 0;
  368.  
  369.         if (currentMode == ZOOM){
  370.             // zoom is based on vertical mouse motion
  371.             cameraZoomSpeed = (float)(mi.y - startY)*bySpeedZoomDragFactor;
  372.         }
  373.         else if (currentMode == PAN){
  374.             // pan is based on motion in both directions
  375.             cameraXPanSpeed = -(float)(mi.x - startX)*bySpeedPanDragFactor;                    
  376.             cameraYPanSpeed =  (float)(mi.y - startY)*bySpeedPanDragFactor;        
  377.         }                                
  378.         else {
  379.             // current mode is ROTATE.
  380.             // rotate is based on both directions.
  381.             cameraHeadingSpeed = -(float)(mi.x - startX)*bySpeedRotateDragFactor;
  382.             cameraPitchSpeed   = -(float)(mi.y - startY)*bySpeedRotateDragFactor;
  383.         }
  384.     }
  385.     
  386.     /**
  387.      * In this dragStyle, values for the camera parameters are saved when the 
  388.      * mouse first is pressed.  Then, a change relative to that value is
  389.      * created based on how far the mouse has moved during the drag.
  390.      */
  391.     void dragMouseByOffsetStyle(MouseInput mi){
  392.         if (currentMode == ZOOM){
  393.             // zoom is based on vertical mouse motion
  394.             unrotatedCameraOffset[2] = startUnrotatedCameraOffset[2] +( (float)(mi.y - startY) )*byOffsetZoomDragFactor;
  395.         }
  396.         else if (currentMode == PAN){
  397.             // pan is based on motion in both directions
  398.             unrotatedCameraOffset[0] = startUnrotatedCameraOffset[0]-( (float)(mi.x - startX) )*byOffsetPanDragFactor;                    
  399.             unrotatedCameraOffset[1] = startUnrotatedCameraOffset[1]+( (float)(mi.y - startY) )*byOffsetPanDragFactor;        
  400.         }                                
  401.         else {
  402.             // current mode is ROTATE.
  403.             // rotate is based on both directions.
  404.             cameraPitch   = startHeadingPitchRoll[1] -( (float)(mi.y - startY)*byOffsetRotateDragFactor );
  405.             cameraHeading = startHeadingPitchRoll[0] -( (float)(mi.x - startX)*byOffsetRotateDragFactor );                            
  406.         }
  407.     }
  408.     
  409.     /**
  410.      * Called when the mouse is released
  411.      */
  412.     void onMouseUp(MouseInput mi){
  413.         // stop rotating the camera
  414.         cameraXPanSpeed = 0;
  415.         cameraYPanSpeed = 0;
  416.         cameraHeadingSpeed = 0;
  417.         cameraPitchSpeed = 0;
  418.         cameraZoomSpeed = 0;
  419.     }
  420.  
  421.     /**
  422.      * Gets the manipulation mode based on the modifier keys in the 
  423.      * MouseInput.
  424.      */
  425.     int getManipulationMode(MouseInput mi){
  426.         if ((mi.modifiers & DeviceInput.CTRL_MASK) != 0){
  427.             // Zoom when control key is down.
  428.             return ZOOM;
  429.         }
  430.         else if ((mi.modifiers & DeviceInput.SHIFT_MASK) != 0){
  431.             // Pan when the shift key is down.
  432.             return PAN;
  433.         }                                
  434.         else {
  435.             // Rotate when neither the shift or control key is down.
  436.             return ROTATE;
  437.         }
  438.     }
  439.     
  440.     /**
  441.      * Utility function for getting the distance between two points
  442.      */
  443.     float getDistance(float[] from, float[] to){
  444.         float x = from[0] - to[0];
  445.         float y = from[1] - to[1];
  446.         float z = from[2] - to[2];
  447.         float dist = (float)Math.sqrt(x*x + y*y + z*z);
  448.         return dist;
  449.     }
  450.     
  451.     /**
  452.      * RenderObserver function. 
  453.      * Does the camera movement calculations after each render.
  454.      * 
  455.      * Note that no onPreRender() method is implemented in this class,
  456.      * even though it is required for all classes implementing RenderObserver.
  457.      * This is because the super class implements RenderObserver fully 
  458.      * and onPreRender is inherited.  
  459.      * Hence this class needs only to override the onPostRender() method, 
  460.      * making sure to call super.onPostRender(r,userData) within the body 
  461.      * of the method.
  462.      */
  463.     public void onPostRender(Renderer r, Object userData){
  464.         super.onPostRender(r,userData);
  465.         
  466.         // If the camera or scene has changed, do necessary initialization.
  467.         checkCameraChange();            
  468.         
  469.         // Only need to do this in relative mode (i.e., when currentDragStyle == BY_SPEED).  
  470.         // In "by offset" mode, these five values have been set exactly based on 
  471.         // the location of the mouse.  
  472.         if(currentDragStyle == BY_SPEED){
  473.             // In relative mode, the location of the mouse gave only a velocity which 
  474.             // must now be multiplied by elapsed time to give an amount of change that 
  475.             // is added to the current amount.
  476.  
  477.             // getFramesPerSecond() returns a rate, frames per second.
  478.             // Flip this (take 1 divided by this number) to get the number of 
  479.             // seconds per frame.  The number of seconds per frame is the elapsed 
  480.             // time since we last rendered. Hence...
  481.             float timeSinceLastRender = 1/getFramesPerSecond();
  482.  
  483.             // To get how much each of the values should change since the last time
  484.             // we rendered a frame, increment each of the 5 parameters 
  485.             // by its velocity times the timeSinceLastRender.  
  486.             cameraHeading += cameraHeadingSpeed*timeSinceLastRender;
  487.             cameraPitch += cameraPitchSpeed*timeSinceLastRender;
  488.             unrotatedCameraOffset[0] += cameraXPanSpeed*timeSinceLastRender;    
  489.             unrotatedCameraOffset[1] += cameraYPanSpeed*timeSinceLastRender;            
  490.             unrotatedCameraOffset[2] += cameraZoomSpeed*timeSinceLastRender;    
  491.         }
  492.  
  493.         // Set the camera orientation based on the current heading, pitch, and roll.
  494.         // First, get axis/angle equivalent of the heading/pitch/roll.  Axis/angle
  495.         // is required to set a rotation field value.
  496.         tempQuat.setEulers(cameraHeading, cameraPitch, cameraRoll);
  497.         float[] camRot = new float[4];
  498.         tempQuat.getAxisAngle(camRot);
  499.         // Next, if the result is different from what's there, set the field value.
  500.         if (camRot[0] != camera.orientation.getValue()[0] ||
  501.             camRot[1] != camera.orientation.getValue()[1] ||
  502.             camRot[2] != camera.orientation.getValue()[2] ||
  503.             camRot[3] != camera.orientation.getValue()[3]){
  504.             camera.orientation.setValue(camRot);
  505.         }
  506.         
  507.         // rotate the camera offset by the camera's rotation, still stored
  508.         // in tempQuat.
  509.         rotatedCameraOffset[0] = unrotatedCameraOffset[0];
  510.         rotatedCameraOffset[1] = unrotatedCameraOffset[1];
  511.         rotatedCameraOffset[2] = unrotatedCameraOffset[2];
  512.         tempQuat.xform(rotatedCameraOffset);
  513.  
  514.         // Add the rotated distance vector to the center of rotation to place the camera.
  515.         float[] camPos = new float[3];
  516.         camPos[0] = bboxCenter[0]+rotatedCameraOffset[0];
  517.         camPos[1] = bboxCenter[1]+rotatedCameraOffset[1];
  518.         camPos[2] = bboxCenter[2]+rotatedCameraOffset[2];
  519.         
  520.         if (camPos[0] != camera.position.getValue()[0] ||
  521.             camPos[1] != camera.position.getValue()[1] ||
  522.             camPos[2] != camera.position.getValue()[2]){
  523.             camera.position.setValue(camPos);
  524.         }
  525.     }
  526.             
  527.     public void checkCameraChange(){
  528.         
  529.         // If scene or camera has changed, re-initialize.
  530.         Viewpoint curCam = (Viewpoint)(applet.getCurrentBindableNode("Viewpoint"));
  531.         Transform curRoot = (Transform)getScene();
  532.         if (curCam != camera || curRoot != root){
  533.             camera = curCam;
  534.             pathToCameraParent = null;
  535.             root = curRoot;
  536.             gotInitCameraOrientation = false;
  537.             
  538.             wantToReset = true;
  539.         }
  540.         
  541.         // Save the current camera orientation if needed
  542.         if (gotInitCameraOrientation == false && camera != null){
  543.             System.arraycopy(camera.orientation.getValue(), 0, initCameraOrientation, 0, 4);
  544.             gotInitCameraOrientation = true;
  545.         }
  546.         
  547.         // Do we want to reset the camera position/orientation?
  548.         if (wantToReset == true){
  549.             cameraRoll = 0;
  550.  
  551.             // put the camera orientation back.
  552.             // Do not pass the reference to initCameraOrientation, or future edits to the
  553.             // camera will change our reference as well. Instead, copy the value and call setValue() to
  554.             // insure notification.
  555.             System.arraycopy(initCameraOrientation, 0, camera.orientation.getValue(), 0, 4);
  556.             camera.orientation.setValue( camera.orientation.getValue());
  557.  
  558.             // Setting this to null insures that bbox and camera position will be 
  559.             // re-initialized below.
  560.             bboxMin = null;
  561.  
  562.             // avoid doing this again
  563.             wantToReset = false;
  564.         }
  565.                      
  566.         if (bboxMin == null){
  567.             // This is the first render.  Establish camera position based on 
  568.             // starting orientation and bbox size.
  569.             
  570.             // Get the center of the current scene's bbox, in world space
  571.             Searcher s = getNewSearcher();
  572.             s.setNode(root);
  573.             Node[] pathToRoot = s.searchFirst(root);
  574.             s.setNode(camera);
  575.             Node[] pathToCamera = s.searchFirst(root);
  576.             
  577.             bboxMin = root.getWorldBBoxMin(pathToRoot);
  578.             bboxMax = root.getWorldBBoxMax(pathToRoot);
  579.             if (bboxMin[0] == 0 &&
  580.                 bboxMin[1] == 0 &&
  581.                 bboxMin[2] == 0 &&
  582.                 bboxMax[0] == 0 &&
  583.                 bboxMax[1] == 0 &&
  584.                 bboxMax[2] == 0){
  585.                 // Scene has not yet calculated a bbox, return and wait until next time.
  586.                 bboxMin = null;
  587.                 return;
  588.             }
  589.                 
  590.             bboxCenter = new float[3];
  591.             
  592.             // Get the path to the camera's parent, if path has length > 1
  593.             if (pathToCamera != null && pathToCamera.length > 1){
  594.                 pathToCameraParent = new Node[pathToCamera.length - 1];
  595.                 System.arraycopy(pathToCamera, 0, pathToCameraParent, 0, pathToCameraParent.length);
  596.             }
  597.                      
  598.             // Set the bbox center.
  599.             // If a rotationCenter is specified, set the bboxCenter to be the rotationCenter
  600.             // and expand the bbox to be centered about the new rotationCenter.
  601.             for(int i = 0; i < 3; i++){
  602.                 if(rotationCenter != null){
  603.                     // rotation center has been specified. use it as the bounding box 
  604.                     // center and stretch the bounding box to fit.
  605.                     if(Math.abs(bboxMax[i] - rotationCenter[i])>Math.abs(bboxMin[i] - rotationCenter[i])){
  606.                         bboxMin[i] = rotationCenter[i]-Math.abs(bboxMax[i] - rotationCenter[i]);
  607.                     }
  608.                     else{
  609.                         bboxMax[i] = rotationCenter[i]+Math.abs(bboxMin[i] - rotationCenter[i]);                
  610.                     }    
  611.                     
  612.                     bboxCenter[i] = rotationCenter[i];
  613.                 }
  614.                 else{        
  615.                     // otherwise use the current bounding box to find the center.
  616.                     bboxCenter[i] = ((bboxMax[i] - bboxMin[i])/2f)+bboxMin[i];
  617.                 
  618.                 }                        
  619.             }    
  620.             
  621.             performInitialCameraPlacement();
  622.  
  623.             // Reset the unrotatedCameraOffset based on the new starting position of the
  624.             // camera.
  625.             unrotatedCameraOffset[0] = 0f;
  626.             unrotatedCameraOffset[1] = 0f;
  627.             unrotatedCameraOffset[2] = getDistance(camera.position.getValue(), bboxCenter);
  628.         }
  629.     }
  630.     
  631.     public void performInitialCameraPlacement() {
  632.         // Perform initial camera placement.
  633.         // Start at the bbox center:
  634.         float[] cameraPos = { bboxCenter[0], bboxCenter[1], bboxCenter[2]};
  635.         // Move back far enough that the largest of width/height/depth of the bbox can be fully seen within the field of view
  636.         // These are given by the equations:   tan(fieldOfView/2) = (width/2)/xbasedZdist;
  637.         //                                     tan(fieldOfView/2) = (height/2)/ybasedZdist;
  638.         //                                     tan(fieldOfView/2) = (depth/2)/zbasedZdist;
  639.         float xbasedZdist = (float)((bboxMax[0] - bboxMin[0])/(2f * Math.tan(camera.fieldOfView.getValue() / 2f)));
  640.         float ybasedZdist = (float)((bboxMax[1] - bboxMin[1])/(2f * Math.tan(camera.fieldOfView.getValue() / 2f)));
  641.         float zbasedZdist = (float)((bboxMax[2] - bboxMin[2])/(2f * Math.tan(camera.fieldOfView.getValue() / 2f)));
  642.         // In addition, need to move back half of the box depth, to situate this viewable 
  643.         // region at the front side of the bbox.  This makes the equations 
  644.         float halfBoxDepth = (bboxMax[2] - bboxMin[2])/2f;
  645.         xbasedZdist += halfBoxDepth;
  646.         ybasedZdist += halfBoxDepth;
  647.         zbasedZdist += halfBoxDepth;
  648.         
  649.         // Use the bigger of the two distances, with some slack for good measure:
  650.         if (xbasedZdist > ybasedZdist && xbasedZdist > zbasedZdist)
  651.             cameraPos[2] += (1f + backupSlack) * xbasedZdist;
  652.         else if ( ybasedZdist > zbasedZdist)
  653.             cameraPos[2] += (1f + backupSlack) * ybasedZdist;
  654.         else
  655.             cameraPos[2] += (1f + backupSlack) * zbasedZdist;
  656.         // If needed, transform position into camera parent space:
  657.         if (pathToCameraParent != null){
  658.             // Second argument means get matrix going from top to bottom of path.
  659.             float[] xfMat = MatUtil.getMatrixAlongPath(pathToCameraParent, false);
  660.             MatUtil.multVecMatrix(xfMat, cameraPos);
  661.         }
  662.         // Now  set it the position in the camera
  663.         camera.position.setValue(cameraPos);
  664.         
  665.         // Set the initial cameraHeading and cameraPitch from the current values in the camera.
  666.         Quaternion initCamQuat = new Quaternion();
  667.         initCamQuat.setAxisAngle(camera.orientation.getValue());
  668.         float[] headingPitchRoll = new float[3];
  669.         initCamQuat.getEulers(headingPitchRoll);
  670.         cameraHeading = headingPitchRoll[0];
  671.         cameraPitch = headingPitchRoll[1];
  672.     }
  673. }
  674.